package com.gitee.wsl.struct.generator.sample

import com.gitee.wsl.ext.base.currentTimeMillis
import com.gitee.wsl.ext.string.toBinaryString
import com.gitee.wsl.struct.generator.sample.CardIdGeneratorable.Companion.getTimeStampSecond
import com.gitee.wsl.struct.generator.sample.CardIdGeneratorable.Companion.getValidationCode
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized
import kotlin.jvm.JvmOverloads


/**
 * 13位数字短卡号生成器，一共43 bit
 *
 * @author liuzhaoming
 * @date 2018/7/11
 */

class ShortCardIdGenerator @JvmOverloads constructor(machineId: Int = 1) :
    CardIdGeneratorable, SynchronizedObject() {
    /**
     * 时间bit数，时间的单位为秒，29 bit位时间可以表示17年
     */
    private val timeBits = 29

    /**
     * 机器编码bit数
     */
    private val machineBits = 3

    /**
     * 每秒序列bit数
     */
    private val sequenceBits = 8

    /**
     * 校验bit位数
     */
    private val validationBits = 3

    /**
     * 上一次时间戳
     */
    private var lastStamp = -1L

    /**
     * 序列
     */
    private var sequence = randomSequence()

    /**
     * 机器编号
     */
    private var machineId = 1L

    /**
     * 时间左移bit数
     */
    private var timeOffset = 0

    /**
     * 机器编码左移bit数
     */
    private var machineOffset = 0

    /**
     * 序列左移bit数
     */
    private var sequenceOffset = 0

    /**
     * 最大序列号
     */
    private var maxSequence = 0L

    /**
     * 最大校验码
     */
    private var maxCode = 0

    /**
     * 开始时间，默认为2019-01-01
     */
    private val startTimeString = "2019-01-01 00:00:00"

    /**
     * 起始时间戳
     */
    private var startTimeStamp = 0L

    init {
        val maxMachineId = (-1 shl machineBits).inv()
        if (machineId > maxMachineId || machineId < 1) {
            throw IllegalArgumentException("Machine id should be between 1 and $maxMachineId")
        }

        this.machineId = machineId.toLong()
        init()
    }

    /**
     * 根据给定的系统编号生成卡号
     *
     * @return 13位卡号
     */

    fun generate(): Long = synchronized(this){
        var curStamp = currentSecond
        if (curStamp < lastStamp) {
            throw IllegalArgumentException("Clock moved backwards. Refusing to generate id")
        }

        if (curStamp == lastStamp) {
            sequence = (sequence + 1) and maxSequence
            if (sequence == 0L) {
                curStamp = nextSecond
            }
        } else {
            sequence = randomSequence()
        }
        lastStamp = curStamp
        val originId = machineId shl machineOffset or ((curStamp - startTimeStamp) shl timeOffset
                ) or (sequence shl sequenceOffset)

        val validationCode: Int = getValidationCode(originId, maxCode)
        return originId + validationCode
    }

    /**
     * 校验卡号是否合法
     *
     * @param id 卡号
     * @return boolean 合法返回true，反之false
     */
    fun validate(id: Long): Boolean {
        if (id > MAX_ID || id < MIN_ID) {
            return false
        }

        return validateCode(id, startTimeStamp, timeBits, timeOffset, validationBits, maxCode)
    }

    /**
     * 解析卡号
     *
     * @param id 卡号
     * @return 解析结果依次是时间戳、机器编码、序列号
     */
    fun parse(id: Long): Array<Long>? {
        if (!validate(id)) {
            return null
        }

        val bitString: String = id.toString().toBinaryString()
        val bitLength = bitString.length
        val timestamp =
            bitString.substring(bitLength - timeOffset - timeBits, bitLength - timeOffset).toLong(2)
        val machineId = bitString.substring(0, bitLength - machineOffset).toLong(2)
        val sequence = bitString.substring(
            bitLength - sequenceOffset - sequenceBits,
            bitLength - sequenceOffset
        ).toLong(2)
        return arrayOf((timestamp + startTimeStamp) * 1000, machineId, sequence)
    }

    /**
     * 数据初始化
     */
    private fun init() {
        sequenceOffset = validationBits
        timeOffset = sequenceOffset + sequenceBits
        machineOffset = timeOffset + timeBits
        maxSequence = (-1L shl sequenceBits).inv()
        startTimeStamp = getTimeStampSecond(startTimeString)
        maxCode = (-1 shl validationBits).inv()
    }

    private val currentSecond: Long
        /**
         * 获取当前时间戳 单位秒
         *
         * @return 时间戳（秒）
         */
        get() = currentTimeMillis / 1000

    private val nextSecond: Long
        /**
         * 获取下一秒钟
         *
         * @return 时间戳（秒）
         */
        get() {
            var second = currentSecond
            while (second <= lastStamp) {
                second = currentSecond
            }
            return second
        }

    companion object {
        private const val MAX_ID = 9999999999999L

        private const val MIN_ID = 1000000000000L
    }
}
